package com.mingyuan.library.application.lending.message;

import com.mingyuan.library.domain.common.event.DomainEvents;
import com.mingyuan.library.domain.lending.book.event.BookDuplicateHoldFound;
import com.mingyuan.library.domain.lending.book.model.entity.AvailableBook;
import com.mingyuan.library.domain.lending.book.model.entity.Book;
import com.mingyuan.library.domain.lending.book.model.entity.BookOnHold;
import com.mingyuan.library.domain.lending.book.model.entity.CheckedOutBook;
import com.mingyuan.library.domain.lending.book.repository.BookRepository;
import com.mingyuan.library.domain.lending.patron.event.*;
import com.mingyuan.library.domain.lending.patron.model.valueobject.PatronId;
import io.vavr.API;
import lombok.AllArgsConstructor;
import org.springframework.context.event.EventListener;

import java.time.Instant;

import static io.vavr.API.$;
import static io.vavr.API.Case;
import static io.vavr.Predicates.instanceOf;

/**
 * 读者事件处理类
 */
@AllArgsConstructor
public class PatronEventsHandler {

    /**
     * 图书仓储相关接口
     */
    private final BookRepository bookRepository;
    /**
     * 领域事件
     */
    private final DomainEvents domainEvents;

    /**
     * 图书预定中处理
     * @author yangzk
     * @param bookPlacedOnHold 图书预定中处理
     * @date 2024/1/16 16:28
     **/
    @EventListener
    void handle(BookPlacedOnHold bookPlacedOnHold) {
        bookRepository.findBy(bookPlacedOnHold.getBookId())
                .map(book -> handleBookPlacedOnHold(book, bookPlacedOnHold))
                .map(this::saveBook);
    }

    /**
     * 图书借阅处理
     * @author yangzk
     * @param bookCheckedOut 图书借阅
     * @date 2024/1/16 16:32
     **/
    @EventListener
    void handle(BookCheckedOut bookCheckedOut) {
        bookRepository.findBy(bookCheckedOut.getBookId())
                .map(book -> handleBookCheckedOut(book, bookCheckedOut))
                .map(this::saveBook);
    }

    /**
     * 图书预定过期处理
     * @author yangzk
     * @param holdExpired 预定过期
     * @date 2024/1/16 16:32
     **/
    @EventListener
    void handle(BookHoldExpired holdExpired) {
        bookRepository.findBy(holdExpired.getBookId())
                .map(book -> handleBookHoldExpired(book, holdExpired))
                .map(this::saveBook);
    }

    /**
     * 取消预定处理
     * @author yangzk
     * @param holdCanceled 取消预定
     * @date 2024/1/16 16:32
     **/
    @EventListener
    void handle(BookHoldCanceled holdCanceled) {
        bookRepository.findBy(holdCanceled.getBookId())
                .map(book -> handleBookHoldCanceled(book,  holdCanceled))
                .map(this::saveBook);
    }
    /**
     * 归还图书处理
     * @author yangzk
     * @param bookReturned 归还图书
     * @date 2024/1/16 16:33
     * @return
     **/
    @EventListener
    void handle(BookReturned bookReturned) {
        bookRepository.findBy(bookReturned.getBookId())
                .map(book -> handleBookReturned(book, bookReturned))
                .map(this::saveBook);
    }


    /**
     * 处理预定中的图书
     * @author yangzk
     * @param
     * @date 2024/1/16 16:30
     * @return
     **/
    private Book handleBookPlacedOnHold(Book book, BookPlacedOnHold bookPlacedOnHold) {
        return API.Match(book).of(
                Case($(instanceOf(AvailableBook.class)), availableBook -> availableBook.handle(bookPlacedOnHold)),
                Case($(instanceOf(BookOnHold.class)), bookOnHold -> raiseDuplicateHoldFoundEvent(bookOnHold, bookPlacedOnHold)),
                Case($(), () -> book)
        );
    }

    /**
     * 触发重复保留事件
     * @author yangzk
     * @param onHold 图书预定
     * @param bookPlacedOnHold 图书预定
     * @date 2024/1/16 16:31
     **/
    private BookOnHold raiseDuplicateHoldFoundEvent(BookOnHold onHold, BookPlacedOnHold bookPlacedOnHold) {
        if(onHold.by(new PatronId(bookPlacedOnHold.getPatronId()))) {
            return onHold;
        }
        domainEvents.publish(
                new BookDuplicateHoldFound(
                        Instant.now(),
                        onHold.getByPatron().getPatronId(),
                        bookPlacedOnHold.getPatronId(),
                        bookPlacedOnHold.getLibraryBranchId(),
                        bookPlacedOnHold.getBookId()));
        return onHold;
    }


    /**
     * 图书预定过期处理
     * @author yangzk
     * @param book 图书信息
     * @param holdExpired 预定过期
     * @date 2024/1/16 16:34
     **/
    private Book handleBookHoldExpired(Book book, BookHoldExpired holdExpired) {
        return API.Match(book).of(
                Case($(instanceOf(BookOnHold.class)), onHold -> onHold.handle(holdExpired)),
                Case($(), () -> book)
        );
    }

    private Book handleBookHoldCanceled(Book book, BookHoldCanceled holdCanceled) {
        return API.Match(book).of(
                Case($(instanceOf(BookOnHold.class)), onHold -> onHold.handle(holdCanceled)),
                Case($(), () -> book)
        );
    }

    private Book handleBookCheckedOut(Book book, BookCheckedOut bookCheckedOut) {
        return API.Match(book).of(
                Case($(instanceOf(BookOnHold.class)), onHold -> onHold.handle(bookCheckedOut)),
                Case($(), () -> book)
        );
    }

    private Book handleBookReturned(Book book, BookReturned bookReturned) {
        return API.Match(book).of(
                Case($(instanceOf(CheckedOutBook.class)), checkedOut -> checkedOut.handle(bookReturned)),
                Case($(), () -> book)
        );
    }

    private Book saveBook(Book book) {
        bookRepository.save(book);
        return book;
    }

}
